还没有笔记
选中页面文字后点击「高亮」按钮添加
COMS W3157
Borowski 博士
https://www.kernel.org/doc/Documentation/process/coding-styl e.rst
https://en.wikipedia.org/wiki/Indentation style
| auto | break | case | char | const | continue | default | do |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| double | else | enum | extern | float | for | goto | if |
| int | long | register | return | short | signed | sizeof | static |
| struct | switch | typedef | union | unsigned | void | volatile | while |
有关更多信息,请参阅 https://www.geeksforgeeks.org/keywords-in-c/。
| 优先级 | | 运算符 | 描述 | 结合性 |
| :--- | :--- | :--- | :--- | :--- |
| | 1 | ++ --
()
[]
.
->
(type)\{list\} | 后缀增量和减量
函数调用
数组下标
结构体和联合体成员访问
通过指针访问结构体和联合体成员
复合字面量 (C99) | 从左到右 |
| | 2 | ++ --
+ -
! ~
(type)
*
&
sizeof
Alignof | 前缀增量和减量 ${ }^{[\text {注 1] }}$
一元加和减
逻辑非和位非
类型转换
间接寻址 (解引用)
取地址
大小 (sizeof) ${ }^{[\text {注 2] }}$
对齐要求 (C11) | 从右到左 |
| 3 | * / % | 乘法、除法和取余 | 从左到右 |
| :--- | :--- | :--- | :--- |
| 4 | + - | 加法和减法 | |
| 5 | <<>> | 位左移和位右移 | |
| 6 | < <=
> >= | 关系运算符 < 和 $\leq$
关系运算符 > 和 $\geq$ | |
| 7 | == != | 关系运算符 = 和 $\neq$ | |
| 8 | & | 按位与 | |
| 9 | ^ | 按位异或(独占或) | |
| 10 | | | 按位或(包含或) | |
| 11 | && | 逻辑与 | |
| 12 | || | 逻辑或 | |
| 13 | ?: | 三元条件运算符 ${ }^{[\text {注 3] }}$ | 从右到左 |
| :--- | :--- | :--- | :--- |
| $14^{[\text {注 4] }}$ | =
+= -=
*= /= %=
<<= >>=
&= ^= |= | 简单赋值
加法和减法赋值
乘法、除法和取余赋值
位左移和位右移赋值
按位与、异或和或赋值 | |
| 15 | , | 逗号 | 从左到右 |
有关更多详细信息,请参阅 https://en.cppreference.com/w/c/language/operator precedence.html。
在 C 语言中,类型转换是一种非常有用的工具,用于将值从一种数据类型更改为另一种数据类型。
我们为什么需要类型转换?
也许我们想要为某个值分配更多内存(例如:int -> double),或者在字母和数字之间切换(例如:char -> int)等。
有 2 种不同的类型转换方法:
两种方法的示例将在下文介绍!



```
int main(int argc, char **argv) {
char var1 = 0x41; // type cast: 0x41 -> 'A'; integer-> char
int var2 = 1.5; // type cast: 1.5 -> 1; float -> integer
// type cast: -1 -> Oxffffffff; signed -> unsigned
unsigned int var3 = -1;
}
```
char $\mathrm{c}=0 \times 41424344$; c 的值是多少?假设未启用 -Werror=overflow。
```
int main(int argc, char **argv) {
char var1 = 0x41; // type cast: Ox41 -> 'A'; integer-> char
int var2 = 1.5; // type cast: 1.5 -> 1; float -> integer
// type cast: -1 -> Oxffffffff; signed -> unsigned
unsigned int var3 = -1;
}
```
char $\mathrm{c}=0 \times 41424344$;
c 的值是多少?$16^{1} \times 4+16^{0} \times 4=68$,或 ' D ' 假设未启用 -Werror=overflow。
为了保证在 C 中将值转换为新类型,请使用类型转换运算符,其语法为 (new_type) value。
```
int main(int argc, char **argv) {
float f;
int a = 20, b = 3;
f = a/b; // f 是什么? 6
f = (float)a/b; // f 现在是什么? ~6.666667
```
}
宏通常用于定义永远不会改变且程序其余部分经常访问的值。
使用 \#define
预处理器在将源代码传递给编译器之前,将 $P /$ 的每个实例替换为 3.14。
```
#define PI 3.14
int main(int argc, char **argv) {
float pi = PI; // pi = 3.14
return 0;
}
```
```
enum week { Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday };
int main(int argc, char **argv) {
enum week today;
today = Wednesday; // today = ?
return 0;
}
```
```
enum week { Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday };
int main(int argc, char **argv) {
enum week today;
today = Wednesday; // today = 3
return 0;
}
```
结构体是不同类型的变量的集合,在单一类型下。
```
struct my_struct {
int x;
int y;
int z;
};
```
```
struct book {
int year;
int month;
int book_id;
}; // 不要忘记这里的 ";"。
int main() {
struct book book1; // 声明 book1 为 Books 类型
book1.book_id = 100; // 访问结构体中的成员
return 0;
}
```
联合体用于在同一内存位置存储不同类型的数据。
```
union my_union {
int i;
short s;
char c;
};
```
联合体需要足够的空间来存储其最大的成员。但是,它只能容纳一个信息;对一个成员的赋值会影响其他成员。
my_union 的大小是多少?
```
union my_union { int i; short s; char c; };
int main(int argc, char **argv) {
union my_union test;
int var0;
test.i = 0;
test.c = 'A';
test.s = 16383;
varO = test.c;
}
```
```
在栈上分配的数组示例:
int a[10]; // type name[size1]
char b[10][100];
struct my_struct c[5][10][20];
```
```
int main(int argc, char **argv) {
int array[10];
array[1] = 100;
array[2] = 200;
array[3] = array[1] + array[2];
array[0] = 1000; // 这是对的吗?
array[10] = 1000; // 这是对的吗?
}
```
```
int main(int argc, char **argv) {
int array[10];
array[1] = 100;
array[2] = 200;
array[3] = array[1] + array[2];
array[0] = 1000; // 是的,数组是基于 0 的。
array[10] = 1000; // 不,有效索引是 0-9。
}
```
数组可以按如下方式进行显式初始化:
```
int a[3];
a[O] = O; a[1] = 1; a[2] = 2;
// 0, 1, 2 分别赋值给 a[0], a[1], a[2]。
int a[3] = { 0, 1, 2 };
// 与 int a[3] = { 0, 1, 2 }; 相同,大小推断。
int a[] = { 0, 1, 2 };
// 与 int a[3] = { 1, 0, 0 }; 相同
int a[3] = { 1 };
// 将所有 100 个元素初始化为 0。
// 第一个元素显式为 0,其余隐式为 0。
int a[100] = { 0 };
```
是一个看起来像函数的运算符。返回类型或变量的字节大小。
```
int x = 56;
// sizeof(x) 和 sizeof(int) 都返回 4
int a[3];
// sizeof(a) 返回 12 (3 乘以 sizeof(int))
```
(C 语言中最重要的概念之一)
内存就像一个长的字节数组。
| 72 | 101 | 108 | 108 | 111 | 32 | 65 | 80 | 33 |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
指针存储一个字节(或字节块的开始)的索引,称为内存地址。
例如,值为 5 的指针将指向上面示例中的值 32。(细节要复杂一些,但这是基本思想。) UNIVERSITY
| 72 | 101 | 108 | 108 | 111 | 32 | 65 | 80 | 33 |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
如果我们有一个名为 p 的指针,它指向地址 5,我们可以:
理解指针的关键在于理解指针本身存储在内存中的某个位置,所以我们可以有一个指向指针的指针!

地址 3 指向地址 6,地址 6 指向地址 1。
int intptr; // 指向一个整数 char charptr; // 指向一个字符 struct my_struct *structptr; // 指向一个结构体 int **intptrptr; // 指向一个整数指针
| | 8 字节
" | | 4 字节 | 4 字节 | | 4 字节 | |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| | | | | | | |
|
| int *p; int a,b,c; a = 1; | □
未初始化 | | 1 | 未初始化 | | 未初始化 | |
| | p | 999 | 1000 | 1004 | b | 1008 | C |

既然我们有一个指向 a 的指针,我们可以使用解引用运算符 (*) 访问和修改 a 的值。
这两行具有相同的效果。

*p 相当于说:“沿着粉色箭头,访问那里的值。”
COLUMBIA

C 中指针的一些更精细的细节:
C 中指针的一些更精细的细节:
```
int *p;
double *x = p; // 编译器错误:不兼容的指针类型
Instead we should write double x = (double ) p.
```
```
void *p;
int x = p; // 编译器错误:无法解引用 void
```
C 中指针的一些更精细的细节:
```
int *p = NULL;
int x = *p; // 运行时段错误
```
```
typedef
typedef unsigned int uint; typedef struct my_struct {
...
} my_struct;
typedef union my_union {
int i;
char c;
double d;
char data[sizeof(double)];
} my_union;
```
Typedefs 缩短了整个项目中的声明!
```
typedef struct book {
int year;
int month;
int book_id;
} book; // do not forget about the ";" here
int main() {
book book1; // struct Book Book1;
book1.book_id = 100;
return 0;
}
```
有三种变量作用域:局部、全局、静态。这些不同作用域级别告诉我们变量可以从何处访问。
```
void func1() {
int i;
i = 1;
}
void func2() {
i = 1; // 无效作用域(我们不能从 func1 外部访问 'i'
// 除非重新声明它)
}
```
```
int g_debug_level;
void func1() {
g_debug_level = 1;
}
void func2() {
g_debug_level = 2;
}
void func3() {
g_debug_level = 3;
}
```
```
static int g_debug_level; // 在 file1.c 中
void func1() { // 在 file1.c 中
g_debug_level = 1; // OK
}
void func2() { // 在 file2.c 中
g_debug_level = 2; // NOT OK
}
```
```
int static_integer() {
static int i = 100;
i++;
return i;
}
int main() {
for (int i = 0; i < 5; i++) {
printf("%d\n", static_integer());
}
}
```
声明函数很容易!
```
int my_func(char arg1, int arg2, float arg3);
// return_type, function_name(arguments list)
```
函数可见性:
```
static int my_func(char arg1, int arg2, float arg3);
```
C 仅使用传值调用!
这意味着子函数所做的更改不会影响父函数。
```
int main() {
int i = 0;
callee(i);
printf("%d\n", i);
return 0;
}
```
```
void callee(int c) {
c = 10;
}
```
这个程序的输出是什么?
C 仅使用传值调用!
这意味着子函数所做的更改不会影响父函数。
```
int main() {
int i = 0;
callee(i);
printf("%d\n", i);
return 0;
}
```
```
void callee(int c) {
c = 10;
}
```
这个程序的输出是什么? 0
我们还可以使用函数的返回值更新变量的值。
```
int main() {
int i = 0;
i = callee(i);
printf("%d\n", i);
return 0;
}
```
```
int callee(int c) {
c = 10;
return c;
}
```
这个程序的输出是什么?
我们还可以使用函数的返回值更新变量的值。
```
int main() {
int i = 0;
i = callee(i);
printf("%d\n", i);
return 0;
}
```
```
int callee(int c) {
c = 10;
return c;
}
```
这个程序的输出是什么? 10
我们还可以使用指针更新变量的值。
```
int main() {
int i = 0;
callee(&i);
printf("%d\n", i);
return 0;
}
```
```
void callee(int *c) {
*c = 10; // 解引用
}
```
这个程序的输出是什么?
我们还可以使用指针更新变量的值。
```
int main() {
int i = 0;
callee(&i);
printf("%d\n", i);
return 0;
}
```
```
void callee(int *c) {
*c = 10; // 解引用
}
```
这个程序的输出是什么? 10
正如我们可以有一个指向内存中其他位置数据的指针变量一样,我们也可以有一个函数指针,它存储函数的地址,允许函数作为参数传递并动态调用。
在本课程中,我们最常使用函数指针来实现多态性。
```
#include
int add(int a, int b) { return a + b; }
void say_hi() { puts("Hi"); }
int main() {
// 声明一个与 add() 函数签名匹配的函数指针。
int (*add_ptr) (int, int);
// 声明一个与 say_hi() 函数签名匹配的函数指针。
void (*say_hi_ptr) ();
// 赋值函数指针。
add_ptr = &add; say_hi_ptr = &say_hi;
// 通过它们的函数指针调用函数。
printf("%d", add_ptr(2, 3));
say_hi_ptr();
return 0;
}
```
```
/**
*/
int int_cmp(int a, int b) {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
}
int binary_search(const int key, const int *values,
const size_t num_elems, int (*cmp) (int, int)) {
...
int result = cmp(key, values[mid]);
}
int array[] = { 1, 4, 7, 18, 90 };
int retval = binary_search(4, array, 5, int_cmp);
```